package com.wang.tool.idem.aop;

import com.wang.tool.idem.IdemConfigCenter;
import com.wang.tool.idem.IdemServer;
import com.wang.tool.idem.core.IdemBody;
import com.wang.tool.idem.core.IdemEntity;
import com.wang.tool.lock.LockConfigCenter;
import com.wang.tool.lock.core.LockBody;
import com.wang.tool.lock.core.LockConfig;
import com.wang.tool.monitor.LinkLogUtil;
import com.wang.tool.util.CommonUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

/**
 * @Author wangshaopeng
 * @Date 2022/5/10
 */
public class IdemAdvice {

    private final IdemServer idemServer;

    private final IdemKeyBuilder keyBuilder;

    public IdemAdvice(IdemServer idemServer, IdemKeyBuilder keyBuilder) {
        if (idemServer==null || keyBuilder == null) {
            throw new IllegalArgumentException("idemServer and keyBuilder can not be null");
        }
        this.idemServer = idemServer;
        this.keyBuilder = keyBuilder;
    }

    /**
     * 配置表
     */
    private static final Map<String, Meta> METAS = new HashMap<>();


    /**
     * 缓存切面
     *
     * @param point
     * @return
     * @throws Throwable
     */
    public Object idemAround(ProceedingJoinPoint point) throws Throwable {
        //类,方法
        Class<?> targetType = point.getTarget().getClass();
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        Meta meta = getOrInitMeta(targetType, method);
        if (!meta.valid) {
            throw new IllegalArgumentException("lock_annotation_invalid");
        }

        LockConfig lockConfig = LockConfigCenter.getLockConfig(meta.namespace);
        if (!lockConfig.isOpen()) {
            LinkLogUtil.info("not use idem,{0}",meta.namespace);
            return point.proceed();
        }

        String idemKey = keyBuilder.build(meta.namespace, meta.keyExpress, point.getArgs());
        if (CommonUtil.isBlank(idemKey)) {
            LinkLogUtil.info("idem key is blank,{0}",meta.namespace);
            return point.proceed();
        }

        IdemBody idemBody = IdemBody.build(meta.namespace,idemKey);
        if (meta.lock) {
            int waitTime = meta.lockWaitTime < 0 ? IdemConfigCenter.getDefLockWaitTime() : meta.lockWaitTime;
            int lockSecond = meta.lockSecond < 0 ? IdemConfigCenter.getDefLockSecond() : meta.lockSecond;
            LockBody lockBody = LockBody.build(meta.namespace,idemKey, waitTime, lockSecond);
            idemBody.setLockBody(lockBody);
        }

        return idemServer.idemDo(idemBody, new Supplier<IdemEntity>() {
            @Override
            public IdemEntity get() {
                try {
                    int ttl = meta.idemTtl < 0 ? IdemConfigCenter.getDefIdemTtl() : meta.idemTtl;
                    Object proceed = point.proceed();
                    return IdemEntity.build(proceed,ttl);
                } catch (Throwable e) {
                    if(e instanceof RuntimeException){
                        throw (RuntimeException)e;
                    }
                    if(e instanceof Error){
                        throw (Error)e;
                    }
                    throw new RuntimeException(e);
                }
            }
        }).getResult();
    }


    /**
     * 取/刷新配置
     *
     * @param targetType 类 非空
     * @param method     方法 非空
     * @return 非空
     */
    private Meta getOrInitMeta(Class<?> targetType, Method method) {
        //类名#方法名[参数类型]
        String key = targetType.getName().concat("#").concat(method.getName()).concat(Arrays.toString(method.getParameterTypes()));
        return METAS.computeIfAbsent(key, k -> {
            Meta meta = new Meta();
            Idem idem = AnnotationUtils.findAnnotation(method, Idem.class);
            if(idem==null){
                meta.valid = false;
                return meta;
            }
            String namespace = idem.namespace();
            if (CommonUtil.isBlank(namespace)) {
                meta.valid = false;
                return meta;
            }
            meta.valid = true;
            meta.namespace = namespace;
            meta.keyExpress=idem.keyExpress();
            meta.idemTtl = idem.idemTtl();
            meta.lock = idem.lock();
            if (meta.lock) {
                meta.lockWaitTime = idem.lockWaitTime();
                meta.lockSecond = idem.lockSecond();
            }
            return meta;
        });
    }


    private static class Meta {

        /**
         * 是否是有效的
         */
        private boolean valid;

        private String namespace;

        private String keyExpress;

        private int idemTtl;


        private boolean lock;

        private int lockWaitTime;

        private int lockSecond;

    }


}
